package es.uca.plii.warrenl0;

import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

/**
 * Esta clase representa un objeto que premite una representación aplanada, tokenizable, y sobre la cual
 * sería posible obtener su representación en M0 realizando la especificación apropiada.
 * @author Rafael Sánchez, Alfonso Sendra
 */
public abstract class EntidadL0 {

    protected Nodo nodoRaiz;
    //protected Map<Integer, List<Nodo>> mapaEnumerado;

    /**
     * Constructor protegido de clase. Sólo las clases que heredan
     * pueden utilizar este constructor. El constructor acepta el nodo raíz de
     * la expresión, y numera los nodos, creando un mapa de profundidades, y
     * posteriormente utilizando éste para la numeración.
     * @param nodo el nodo raiz del árbo sintáctico.
     */
    protected EntidadL0(Nodo nodo) throws Exception{

        if(nodo==null)
            throw new Exception("El nodo inicial es nulo");

        Map<Integer, List<Nodo>> mapaEnumerado = new TreeMap<Integer, List<Nodo>>();
        nodoRaiz = nodo;
        nodoRaiz.numerarProfundidades(0, new ExtractorProfundidadesImpl(mapaEnumerado));
        numerarNodos(mapaEnumerado);
    }


    /**
     * Numera los nodos en el orden dado por un recorrido en anchura descendente
     * utilizando un mapa con las profundidades halladas.
     * @param mapaEnumerado el mapa de profundidades de los nodos del árbol
     */
    private void numerarNodos(Map<Integer, List<Nodo>> mapaEnumerado)
    {
        int numero = 1;
        Set<Integer> profundidades = mapaEnumerado.keySet();
        for(Integer profundidad : profundidades)
        {
            for(Nodo nodo : mapaEnumerado.get(profundidad))
            {
                //Para no numerar de nuevo los nodos variables que ya han aparecido
                if (nodo.getTipo() == Nodo.NODOVARIABLE) {
                    for(Integer profundidadVar : profundidades)
                        for(Nodo nodoVar : mapaEnumerado.get(profundidadVar))
                            if (nodoVar.equals(nodo))
                                if (nodoVar.getNumero() == null)
                                    nodo.setNumero(numero++);
                                else
                                    nodo.setNumero(nodoVar.getNumero());
                } else
                    nodo.setNumero(numero++);
            }
        }
    }

    /**
     * Devuelve el nodo raiz de la expresión
     * @return el nodo raiz
     */
    public Nodo getNodoRaiz() {
        return nodoRaiz;
    }

    /**
     * Crea la forma aplanada (Flattened form), teniendo en cuenta el orden
     * en el cual los nodos han de ser especificados a través de un comparador
     * de enteros, que deberá implementarse.
     * @return la forma aplanada como una cadena de caracteres
     */
    public String flattenedForm() {

        StringBuilder tr = new StringBuilder();
        Map<Integer,String> flt = new TreeMap<Integer,String>(comparadorOrdenNodos());
        this.nodoRaiz.obtenerFlattenedForms(flt);
        for(Integer i : flt.keySet())
        {
            tr.append("X");
            tr.append(i);
            tr.append("=");
            tr.append(flt.get(i));
            tr.append("\n");
        }
        return tr.toString();
    }

    /**
     * Crea la forma tokenizada, teniendo en cuenta el orden
     * en el cual los nodos han de ser especificados a través de un comparador
     * de enteros, que deberá implementarse.
     * @return la forma tokenizada como una cadena de caracteres
     */
    public String tokenizedForm() {

        StringBuilder tr = new StringBuilder();
        Map<Integer,String> flt = new TreeMap<Integer,String>(comparadorOrdenNodos());
        this.nodoRaiz.obtenerTokenizedForms(flt);
        int tamano = flt.keySet().size();
        int contador=0;
        for(Integer i : flt.keySet())
        {
            tr.append("X");
            tr.append(i);
            tr.append("=");
            tr.append(flt.get(i));
            contador++;
            if(contador < tamano)
                tr.append(",");
        }
        tr.append(".");
        return tr.toString();
    }

    /**
     * Provee un comparador de enteros para la presentación de las formas
     * aplanadas y tokenizadas y de la representación M0. A implementar por la
     * clase que extienda a ésta.
     * @return el orden correcto
     */
    protected abstract Comparator<Integer> comparadorOrdenNodos();

    /**
     * Crea la representación en instrucciones M0, teniendo en cuenta el orden
     * en el cual los nodos han de ser especificados a través de un comparador
     * de enteros, que deberá implementarse por la clase que extienda a ésta.
     * @return la representación en instrucciones M0 como una cadena de caracteres
     */
    protected abstract String instructionForm();

}
